package org.express.portal.template;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;

import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeInstance;
import org.express.portal.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Template factory using Velocity.
 */
public class VelocityTemplateFactory extends TemplateFactory
{
	private final Logger log = LoggerFactory.getLogger(getClass());

	// velocity runtime instance as singleton:
	private RuntimeInstance rtInstance;

	private String inputEncoding = "UTF-8";
	private String outputEncoding = "UTF-8";

	/**
	 * Get input encoding of template. Default to UTF-8, can be specified by
	 * 'input.encoding' in velocity.properties.
	 */
	public String getInputEncoding()
	{
		return inputEncoding;
	}

	/**
	 * Get output encoding of template. Default to UTF-8, can be specified by
	 * 'output.encoding' in velocity.properties.
	 */
	public String getOutputEncoding()
	{
		return outputEncoding;
	}

	public Template loadTemplate(String path) throws Exception
	{
		if (log.isDebugEnabled())
			log.debug("Load Velocity template '" + path + "'.");
		return new VelocityTemplate(rtInstance.getTemplate(path, inputEncoding), null, outputEncoding);
	}

	public void init(Config config)
	{
		String webAppPath = config.getServletContext().getRealPath("/");
		if (webAppPath == null)
		{
			String err = "Cannot get web application path. Are you deploy the application as a .war file?";
			log.warn(err);
			throw new ExceptionInInitializerError(err);
		}
		if (!webAppPath.endsWith("/") && !webAppPath.endsWith("\\"))
			webAppPath = webAppPath + File.separator;
		log.info("Detect web application path: " + webAppPath);
		log.info("init VelocityTemplateFactory...");
		rtInstance = new RuntimeInstance();
		// read property file:
		Properties props = readProperties(webAppPath + "/WEB-INF/velocity.properties");
		checkProperty(props);
		Set<?> keys = props.keySet();
		for (Object key : keys)
		{
			if (key instanceof String)
			{
				Object value = props.get(key);
				rtInstance.addProperty((String) key, value);
			}
		}
		try
		{
			if (rtInstance.getProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH) == null)
			{
				rtInstance.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, webAppPath);
				log.info("Using web application context path as velocity template root path: " + webAppPath);
			}
			else
			{
				log.info("Velocity property \"" + RuntimeConstants.FILE_RESOURCE_LOADER_PATH + "\" has been specified in \"/WEB-INF/velocity.properties\".");
				log.info("To using web application context path, remove property \"" + RuntimeConstants.FILE_RESOURCE_LOADER_PATH
						+ "\" from configuration file.");
			}
			rtInstance.init();
			afterVelocityEngineInit(rtInstance);
		}
		catch (Exception e)
		{
			log.error("VelocityTemplateFactory init failed.", e);
			throw new ExceptionInInitializerError(e);
		}
	}

	void checkProperty(Properties props)
	{
		this.inputEncoding = setDefaultPropertyIfNotExist(props, "input.encoding", "UTF-8");
		this.outputEncoding = setDefaultPropertyIfNotExist(props, "output.encoding", "UTF-8");
	}

	String setDefaultPropertyIfNotExist(Properties props, String key, String defaultValue)
	{
		String value = props.getProperty(key);
		if (value == null || value.length() == 0)
		{
			props.setProperty(key, defaultValue);
		}
		return props.getProperty(key);
	}

	protected void afterVelocityEngineInit(RuntimeInstance instance)
	{
		// let subclass do some work
	}

	/**
	 * Read velocity properties from file.
	 * 
	 * @param file Velocity configuration file.
	 * @return Velocity Properties.
	 */
	protected Properties readProperties(String file)
	{
		Properties props = new Properties();
		InputStream input = null;
		try
		{
			input = new BufferedInputStream(new FileInputStream(file));
			props.load(input);
		}
		catch (IOException ioe)
		{
			log.warn("Read properties file failed.", ioe);
		}
		finally
		{
			if (input != null)
			{
				try
				{
					input.close();
				}
				catch (IOException e)
				{
				}
			}
		}
		return props;
	}
}
